/*
 * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.java.swing.plaf.windows;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.lang.ref.*;
import java.util.*;
import javax.swing.plaf.basic.*;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;

import static com.sun.java.swing.plaf.windows.TMSchema.*;
import static com.sun.java.swing.plaf.windows.XPStyle.Skin;


/**
 * Windows rendition of the component.
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases.  The current serialization support is appropriate
 * for short term storage or RMI between applications running the same
 * version of Swing.  A future release of Swing will provide support for
 * long term persistence.
 */
public class WindowsScrollBarUI extends BasicScrollBarUI {

  private Grid thumbGrid;
  private Grid highlightGrid;
  private Dimension horizontalThumbSize;
  private Dimension verticalThumbSize;

  /**
   * Creates a UI for a JScrollBar.
   *
   * @param c the text field
   * @return the UI
   */
  public static ComponentUI createUI(JComponent c) {
    return new WindowsScrollBarUI();
  }

  protected void installDefaults() {
    super.installDefaults();

    XPStyle xp = XPStyle.getXP();
    if (xp != null) {
      scrollbar.setBorder(null);
      horizontalThumbSize = getSize(scrollbar, xp, Part.SBP_THUMBBTNHORZ);
      verticalThumbSize = getSize(scrollbar, xp, Part.SBP_THUMBBTNVERT);
    } else {
      horizontalThumbSize = null;
      verticalThumbSize = null;
    }
  }

  private static Dimension getSize(Component component, XPStyle xp, Part part) {
    Skin skin = xp.getSkin(component, part);
    return new Dimension(skin.getWidth(), skin.getHeight());
  }

  @Override
  protected Dimension getMinimumThumbSize() {
    if ((horizontalThumbSize == null) || (verticalThumbSize == null)) {
      return super.getMinimumThumbSize();
    }
    return JScrollBar.HORIZONTAL == scrollbar.getOrientation()
        ? horizontalThumbSize
        : verticalThumbSize;
  }

  public void uninstallUI(JComponent c) {
    super.uninstallUI(c);
    thumbGrid = highlightGrid = null;
  }

  protected void configureScrollBarColors() {
    super.configureScrollBarColors();
    Color color = UIManager.getColor("ScrollBar.trackForeground");
    if (color != null && trackColor != null) {
      thumbGrid = Grid.getGrid(color, trackColor);
    }

    color = UIManager.getColor("ScrollBar.trackHighlightForeground");
    if (color != null && trackHighlightColor != null) {
      highlightGrid = Grid.getGrid(color, trackHighlightColor);
    }
  }

  protected JButton createDecreaseButton(int orientation) {
    return new WindowsArrowButton(orientation,
        UIManager.getColor("ScrollBar.thumb"),
        UIManager.getColor("ScrollBar.thumbShadow"),
        UIManager.getColor("ScrollBar.thumbDarkShadow"),
        UIManager.getColor("ScrollBar.thumbHighlight"));
  }

  protected JButton createIncreaseButton(int orientation) {
    return new WindowsArrowButton(orientation,
        UIManager.getColor("ScrollBar.thumb"),
        UIManager.getColor("ScrollBar.thumbShadow"),
        UIManager.getColor("ScrollBar.thumbDarkShadow"),
        UIManager.getColor("ScrollBar.thumbHighlight"));
  }

  /**
   * {@inheritDoc}
   *
   * @since 1.6
   */
  @Override
  protected ArrowButtonListener createArrowButtonListener() {
    // we need to repaint the entire scrollbar because state change for each
    // button causes a state change for the thumb and other button on Vista
    if (XPStyle.isVista()) {
      return new ArrowButtonListener() {
        public void mouseEntered(MouseEvent evt) {
          repaint();
          super.mouseEntered(evt);
        }

        public void mouseExited(MouseEvent evt) {
          repaint();
          super.mouseExited(evt);
        }

        private void repaint() {
          scrollbar.repaint();
        }
      };
    } else {
      return super.createArrowButtonListener();
    }
  }

  protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) {
    boolean v = (scrollbar.getOrientation() == JScrollBar.VERTICAL);

    XPStyle xp = XPStyle.getXP();
    if (xp != null) {
      JScrollBar sb = (JScrollBar) c;
      State state = State.NORMAL;
      // Pending: Implement rollover (hot) and pressed
      if (!sb.isEnabled()) {
        state = State.DISABLED;
      }
      Part part = v ? Part.SBP_LOWERTRACKVERT : Part.SBP_LOWERTRACKHORZ;
      xp.getSkin(sb, part).paintSkin(g, trackBounds, state);
    } else if (thumbGrid == null) {
      super.paintTrack(g, c, trackBounds);
    } else {
      thumbGrid.paint(g, trackBounds.x, trackBounds.y, trackBounds.width,
          trackBounds.height);
      if (trackHighlight == DECREASE_HIGHLIGHT) {
        paintDecreaseHighlight(g);
      } else if (trackHighlight == INCREASE_HIGHLIGHT) {
        paintIncreaseHighlight(g);
      }
    }
  }

  protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
    boolean v = (scrollbar.getOrientation() == JScrollBar.VERTICAL);

    XPStyle xp = XPStyle.getXP();
    if (xp != null) {
      JScrollBar sb = (JScrollBar) c;
      State state = State.NORMAL;
      if (!sb.isEnabled()) {
        state = State.DISABLED;
      } else if (isDragging) {
        state = State.PRESSED;
      } else if (isThumbRollover()) {
        state = State.HOT;
      } else if (XPStyle.isVista()) {
        if ((incrButton != null && incrButton.getModel().isRollover()) ||
            (decrButton != null && decrButton.getModel().isRollover())) {
          state = State.HOVER;
        }
      }
      // Paint thumb
      Part thumbPart = v ? Part.SBP_THUMBBTNVERT : Part.SBP_THUMBBTNHORZ;
      xp.getSkin(sb, thumbPart).paintSkin(g, thumbBounds, state);
      // Paint gripper
      Part gripperPart = v ? Part.SBP_GRIPPERVERT : Part.SBP_GRIPPERHORZ;
      Skin skin = xp.getSkin(sb, gripperPart);
      Insets gripperInsets = xp.getMargin(c, thumbPart, null, Prop.CONTENTMARGINS);
      if (gripperInsets == null ||
          (v && (thumbBounds.height - gripperInsets.top -
              gripperInsets.bottom >= skin.getHeight())) ||
          (!v && (thumbBounds.width - gripperInsets.left -
              gripperInsets.right >= skin.getWidth()))) {
        skin.paintSkin(g,
            thumbBounds.x + (thumbBounds.width - skin.getWidth()) / 2,
            thumbBounds.y + (thumbBounds.height - skin.getHeight()) / 2,
            skin.getWidth(), skin.getHeight(), state);
      }
    } else {
      super.paintThumb(g, c, thumbBounds);
    }
  }


  protected void paintDecreaseHighlight(Graphics g) {
    if (highlightGrid == null) {
      super.paintDecreaseHighlight(g);
    } else {
      Insets insets = scrollbar.getInsets();
      Rectangle thumbR = getThumbBounds();
      int x, y, w, h;

      if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
        x = insets.left;
        y = decrButton.getY() + decrButton.getHeight();
        w = scrollbar.getWidth() - (insets.left + insets.right);
        h = thumbR.y - y;
      } else {
        x = decrButton.getX() + decrButton.getHeight();
        y = insets.top;
        w = thumbR.x - x;
        h = scrollbar.getHeight() - (insets.top + insets.bottom);
      }
      highlightGrid.paint(g, x, y, w, h);
    }
  }


  protected void paintIncreaseHighlight(Graphics g) {
    if (highlightGrid == null) {
      super.paintDecreaseHighlight(g);
    } else {
      Insets insets = scrollbar.getInsets();
      Rectangle thumbR = getThumbBounds();
      int x, y, w, h;

      if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
        x = insets.left;
        y = thumbR.y + thumbR.height;
        w = scrollbar.getWidth() - (insets.left + insets.right);
        h = incrButton.getY() - y;
      } else {
        x = thumbR.x + thumbR.width;
        y = insets.top;
        w = incrButton.getX() - x;
        h = scrollbar.getHeight() - (insets.top + insets.bottom);
      }
      highlightGrid.paint(g, x, y, w, h);
    }
  }


  /**
   * {@inheritDoc}
   *
   * @since 1.6
   */
  @Override
  protected void setThumbRollover(boolean active) {
    boolean old = isThumbRollover();
    super.setThumbRollover(active);
    // we need to repaint the entire scrollbar because state change for thumb
    // causes state change for incr and decr buttons on Vista
    if (XPStyle.isVista() && active != old) {
      scrollbar.repaint();
    }
  }

  /**
   * WindowsArrowButton is used for the buttons to position the
   * document up/down. It differs from BasicArrowButton in that the
   * preferred size is always a square.
   */
  private class WindowsArrowButton extends BasicArrowButton {

    public WindowsArrowButton(int direction, Color background, Color shadow,
        Color darkShadow, Color highlight) {
      super(direction, background, shadow, darkShadow, highlight);
    }

    public WindowsArrowButton(int direction) {
      super(direction);
    }

    public void paint(Graphics g) {
      XPStyle xp = XPStyle.getXP();
      if (xp != null) {
        ButtonModel model = getModel();
        Skin skin = xp.getSkin(this, Part.SBP_ARROWBTN);
        State state = null;

        boolean jointRollover = XPStyle.isVista() && (isThumbRollover() ||
            (this == incrButton && decrButton.getModel().isRollover()) ||
            (this == decrButton && incrButton.getModel().isRollover()));

        // normal, rollover, pressed, disabled
        if (model.isArmed() && model.isPressed()) {
          switch (direction) {
            case NORTH:
              state = State.UPPRESSED;
              break;
            case SOUTH:
              state = State.DOWNPRESSED;
              break;
            case WEST:
              state = State.LEFTPRESSED;
              break;
            case EAST:
              state = State.RIGHTPRESSED;
              break;
          }
        } else if (!model.isEnabled()) {
          switch (direction) {
            case NORTH:
              state = State.UPDISABLED;
              break;
            case SOUTH:
              state = State.DOWNDISABLED;
              break;
            case WEST:
              state = State.LEFTDISABLED;
              break;
            case EAST:
              state = State.RIGHTDISABLED;
              break;
          }
        } else if (model.isRollover() || model.isPressed()) {
          switch (direction) {
            case NORTH:
              state = State.UPHOT;
              break;
            case SOUTH:
              state = State.DOWNHOT;
              break;
            case WEST:
              state = State.LEFTHOT;
              break;
            case EAST:
              state = State.RIGHTHOT;
              break;
          }
        } else if (jointRollover) {
          switch (direction) {
            case NORTH:
              state = State.UPHOVER;
              break;
            case SOUTH:
              state = State.DOWNHOVER;
              break;
            case WEST:
              state = State.LEFTHOVER;
              break;
            case EAST:
              state = State.RIGHTHOVER;
              break;
          }
        } else {
          switch (direction) {
            case NORTH:
              state = State.UPNORMAL;
              break;
            case SOUTH:
              state = State.DOWNNORMAL;
              break;
            case WEST:
              state = State.LEFTNORMAL;
              break;
            case EAST:
              state = State.RIGHTNORMAL;
              break;
          }
        }

        skin.paintSkin(g, 0, 0, getWidth(), getHeight(), state);
      } else {
        super.paint(g);
      }
    }

    public Dimension getPreferredSize() {
      int size = 16;
      if (scrollbar != null) {
        switch (scrollbar.getOrientation()) {
          case JScrollBar.VERTICAL:
            size = scrollbar.getWidth();
            break;
          case JScrollBar.HORIZONTAL:
            size = scrollbar.getHeight();
            break;
        }
        size = Math.max(size, 5);
      }
      return new Dimension(size, size);
    }
  }


  /**
   * This should be pulled out into its own class if more classes need to
   * use it.
   * <p>
   * Grid is used to draw the track for windows scrollbars. Grids
   * are cached in a HashMap, with the key being the rgb components
   * of the foreground/background colors. Further the Grid is held through
   * a WeakRef so that it can be freed when no longer needed. As the
   * Grid is rather expensive to draw, it is drawn in a BufferedImage.
   */
  private static class Grid {

    private static final int BUFFER_SIZE = 64;
    private static HashMap<String, WeakReference<Grid>> map;

    private BufferedImage image;

    static {
      map = new HashMap<String, WeakReference<Grid>>();
    }

    public static Grid getGrid(Color fg, Color bg) {
      String key = fg.getRGB() + " " + bg.getRGB();
      WeakReference<Grid> ref = map.get(key);
      Grid grid = (ref == null) ? null : ref.get();
      if (grid == null) {
        grid = new Grid(fg, bg);
        map.put(key, new WeakReference<Grid>(grid));
      }
      return grid;
    }

    public Grid(Color fg, Color bg) {
      int cmap[] = {fg.getRGB(), bg.getRGB()};
      IndexColorModel icm = new IndexColorModel(8, 2, cmap, 0, false, -1,
          DataBuffer.TYPE_BYTE);
      image = new BufferedImage(BUFFER_SIZE, BUFFER_SIZE,
          BufferedImage.TYPE_BYTE_INDEXED, icm);
      Graphics g = image.getGraphics();
      try {
        g.setClip(0, 0, BUFFER_SIZE, BUFFER_SIZE);
        paintGrid(g, fg, bg);
      } finally {
        g.dispose();
      }
    }

    /**
     * Paints the grid into the specified Graphics at the specified
     * location.
     */
    public void paint(Graphics g, int x, int y, int w, int h) {
      Rectangle clipRect = g.getClipBounds();
      int minX = Math.max(x, clipRect.x);
      int minY = Math.max(y, clipRect.y);
      int maxX = Math.min(clipRect.x + clipRect.width, x + w);
      int maxY = Math.min(clipRect.y + clipRect.height, y + h);

      if (maxX <= minX || maxY <= minY) {
        return;
      }
      int xOffset = (minX - x) % 2;
      for (int xCounter = minX; xCounter < maxX;
          xCounter += BUFFER_SIZE) {
        int yOffset = (minY - y) % 2;
        int width = Math.min(BUFFER_SIZE - xOffset,
            maxX - xCounter);

        for (int yCounter = minY; yCounter < maxY;
            yCounter += BUFFER_SIZE) {
          int height = Math.min(BUFFER_SIZE - yOffset,
              maxY - yCounter);

          g.drawImage(image, xCounter, yCounter,
              xCounter + width, yCounter + height,
              xOffset, yOffset,
              xOffset + width, yOffset + height, null);
          if (yOffset != 0) {
            yCounter -= yOffset;
            yOffset = 0;
          }
        }
        if (xOffset != 0) {
          xCounter -= xOffset;
          xOffset = 0;
        }
      }
    }

    /**
     * Actually renders the grid into the Graphics <code>g</code>.
     */
    private void paintGrid(Graphics g, Color fg, Color bg) {
      Rectangle clipRect = g.getClipBounds();
      g.setColor(bg);
      g.fillRect(clipRect.x, clipRect.y, clipRect.width,
          clipRect.height);
      g.setColor(fg);
      g.translate(clipRect.x, clipRect.y);
      int width = clipRect.width;
      int height = clipRect.height;
      int xCounter = clipRect.x % 2;
      for (int end = width - height; xCounter < end; xCounter += 2) {
        g.drawLine(xCounter, 0, xCounter + height, height);
      }
      for (int end = width; xCounter < end; xCounter += 2) {
        g.drawLine(xCounter, 0, width, width - xCounter);
      }

      int yCounter = ((clipRect.x % 2) == 0) ? 2 : 1;
      for (int end = height - width; yCounter < end; yCounter += 2) {
        g.drawLine(0, yCounter, width, yCounter + width);
      }
      for (int end = height; yCounter < end; yCounter += 2) {
        g.drawLine(0, yCounter, height - yCounter, height);
      }
      g.translate(-clipRect.x, -clipRect.y);
    }
  }
}
